package net.lrstudios.android.pachi;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import java.util.regex.Pattern;

import lrstudios.games.ego.lib.ExternalGtpEngine;
import lrstudios.games.ego.lib.Utils;
import lrstudios.util.android.AndroidUtils;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;

public class PachiEngine extends ExternalGtpEngine {

    private static final String TAG = "PachiEngine";

    /**
     * Increment this counter each time you update the pachi executable (this
     * will force the app to extract it again - I didn't find a better way to
     * know if an android resource has been updated).
     */
    private static final int EXE_VERSION = 2;

    private static final int BOOK_VERSION = 1;

    private static final String ENGINE_NAME = "Pachi";
    private static final String ENGINE_VERSION = "10.99";

    private static final String PREF_KEY_EXE_VERSION = "pachi_exe_version";

    private static final String PREF_KEY_BOOK_VERSION = "pachi_book_version";

    private int _time = 10;
    private int _maxTreeSize = 128;

    public PachiEngine(Context context) {
        super(context);
        long totalRam = AndroidUtils.getTotalRam(context);
        // The amount of RAM used by pachi (adjustable with max_tree_size)
        // should not
        // be too high compared to the total RAM available, because Android can
        // kill a
        // process at any time if it uses too much memory.
        if (totalRam > 0)
            _maxTreeSize = Math.max(128, (int) Math.round(totalRam / 1024.0 / 1024.0 * 0.25));
    }

    @Override
    public boolean init(Properties properties) {
        int level = Utils.tryParseInt(properties.getProperty("level"), 5);
        int boardsize = Utils.tryParseInt(properties.getProperty("boardsize"), 9);

        _time = Math.min(30, (int) Math.round((boardsize + 1) / 4 * (0.5 + level / 10.0)));
        return super.init(properties);
    }

    /**
     * Gets the number of cores available in this device, across all processors.
     * Requires: Ability to peruse the filesystem at "/sys/devices/system/cpu"
     * 
     * @return The number of cores, or 1 if failed to get result
     */
    private int getNumCores() {
        // Private Class to display only CPU devices in the directory listing
        class CpuFilter implements FileFilter {
            @Override
            public boolean accept(File pathname) {
                // Check if filename is "cpu", followed by a single digit number
                if (Pattern.matches("cpu[0-9]", pathname.getName())) {
                    return true;
                }
                return false;
            }
        }

        try {
            // Get directory containing CPU info
            File dir = new File("/sys/devices/system/cpu/");
            // Filter to only list the devices we care about
            File[] files = dir.listFiles(new CpuFilter());
            Log.d(TAG, "CPU Count: " + files.length);
            // Return the number of cores (virtual CPU devices)
            return files.length;
        } catch (Exception e) {
            // Print exception
            Log.d(TAG, "CPU Count: Failed.");
            e.printStackTrace();
            // Default to return 1 core
            return 1;
        }
    }

    @Override
    protected String[] getProcessArgs() {
        int coresNum = getNumCores();
        File openingBookFile = getOpeningBookFile();
        Log.e(TAG, "Set max_tree_size = " + _maxTreeSize);
        Log.e(TAG, "Set time = " + _time);
        Log.e(TAG, "Set threads = " + coresNum);
        Log.e(TAG, "Set openingBook = " + openingBookFile.getAbsolutePath());
        return new String[] { "-f", openingBookFile.getAbsolutePath(), "-t", "" + _time,
                "threads=" + Math.max(1, coresNum - 1), "max_tree_size=" + _maxTreeSize };
    }

    private File getOpeningBookFile() {
        File dir = _context.getDir("engines", Context.MODE_PRIVATE);
        File file = new File(dir, "book");

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(_context);
        int version = prefs.getInt(PREF_KEY_BOOK_VERSION, 0);
        if (version < BOOK_VERSION) {
            if (file.exists())
                file.delete();
            InputStream inputStream = null;
            OutputStream outputStream = null;
            try {
                outputStream = new BufferedOutputStream(new FileOutputStream(file), 4096);
                inputStream = new BufferedInputStream(_context.getResources().openRawResource(
                        R.raw.book), 4096);
                Utils.copyStream(inputStream, outputStream, 4096);

                try {
                    // file.setExecutable(true); TODO test this instead of chmod
                    new ProcessBuilder("chmod", "744", file.getAbsolutePath()).start().waitFor();
                    SharedPreferences.Editor editor = prefs.edit();
                    editor.putInt(PREF_KEY_BOOK_VERSION, BOOK_VERSION);
                    editor.commit();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } catch (IOException e) { // TODO handle file extracting errors
                e.printStackTrace();
            } finally {
                Utils.closeObject(inputStream);
                Utils.closeObject(outputStream);
            }
        }

        return file;
    }

    @Override
    protected File getEngineFile() {
        File dir = _context.getDir("engines", Context.MODE_PRIVATE);
        File file = new File(dir, "pachi");

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(_context);
        int version = prefs.getInt(PREF_KEY_EXE_VERSION, 0);
        if (version < EXE_VERSION) {
            if (file.exists())
                file.delete();
            InputStream inputStream = null;
            OutputStream outputStream = null;
            try {
                outputStream = new BufferedOutputStream(new FileOutputStream(file), 4096);
                inputStream = new BufferedInputStream(_context.getResources().openRawResource(
                        R.raw.pachi), 4096);
                Utils.copyStream(inputStream, outputStream, 4096);

                try {
                    // file.setExecutable(true); TODO test this instead of chmod
                    new ProcessBuilder("chmod", "744", file.getAbsolutePath()).start().waitFor();
                    SharedPreferences.Editor editor = prefs.edit();
                    editor.putInt(PREF_KEY_EXE_VERSION, EXE_VERSION);
                    editor.commit();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } catch (IOException e) { // TODO handle file extracting errors
                e.printStackTrace();
            } finally {
                Utils.closeObject(inputStream);
                Utils.closeObject(outputStream);
            }
        }

        return file;
    }

    @Override
    public String getName() {
        return ENGINE_NAME;
    }

    @Override
    public String getVersion() {
        return ENGINE_VERSION;
    }
}
